/*-------------------------------------------------------------------------
 * drawElements C++ Base Library
 * -----------------------------
 *
 * Copyright 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *//*!
 * \file
 * \brief Filesystem path class.
 *//*--------------------------------------------------------------------*/

#include "deFilePath.hpp"

#include <vector>
#include <stdexcept>

#include <sys/stat.h>
#include <sys/types.h>

#if (DE_OS == DE_OS_WIN32)
#	define VC_EXTRALEAN
#	define WIN32_LEAN_AND_MEAN
#	define NOMINMAX
#	include <windows.h>
#endif

using std::string;

namespace de
{

#if (DE_OS == DE_OS_WIN32)
	const std::string FilePath::separator = "\\";
#else
	const std::string FilePath::separator = "/";
#endif

FilePath::FilePath (const std::vector<std::string>& components)
{
	for (int ndx = 0; ndx < (int)components.size(); ndx++)
	{
		if (ndx > 0)
			m_path += separator;
		m_path += components[ndx];
	}
}

void FilePath::split (std::vector<std::string>& components) const
{
	components.clear();

	int curCompStart = 0;
	int pos;

	for (pos = 0; pos < (int)m_path.length(); pos++)
	{
		char c = m_path[pos];

		if (isSeparator(c))
		{
			if (pos - curCompStart > 0)
				components.push_back(m_path.substr(curCompStart, pos - curCompStart));
			
			curCompStart = pos+1;
		}
	}

	if (pos - curCompStart > 0)
		components.push_back(m_path.substr(curCompStart, pos - curCompStart));
}

FilePath& FilePath::normalize (void)
{
	bool						rootPath	= isRootPath();
	bool						winNetPath	= isWinNetPath();
	bool						hasDrive	= beginsWithDrive();
	std::vector<std::string>	components;
	split(components);

	m_path = "";

	int numUp = 0;

	// Do in reverse order and eliminate any . or .. components
	for (int ndx = (int)components.size()-1; ndx >= 0; ndx--)
	{
		const std::string comp = components[ndx];
		if (comp == "..")
			numUp += 1;
		else if (comp == ".")
			continue;
		else if (numUp > 0)
			numUp -= 1; // Skip part
		else
		{
			if (m_path.length() > 0)
				m_path = comp + separator + m_path;
			else
				m_path = comp;
		}
	}

	// Append necessary ".." components
	while (numUp--)
	{
		if (m_path.length() > 0)
			m_path = ".." + separator + m_path;
		else
			m_path = "..";
	}
	
	if (winNetPath)
		m_path = separator + separator + m_path;
	else if (rootPath && !hasDrive)
		m_path = separator + m_path;

	if (m_path.length() == 0 && !components.empty())
		m_path = "."; // Composed of "." components only

	return *this;
}

FilePath FilePath::normalize (const FilePath& path)
{
	return FilePath(path).normalize();
}

std::string FilePath::getBaseName (void) const
{
	std::vector<std::string> components;
	split(components);
	return !components.empty() ? components[components.size()-1] : std::string("");
}

std::string	FilePath::getDirName (void) const
{
	std::vector<std::string> components;
	split(components);
	if (components.size() > 1)
	{
		components.pop_back();
		return FilePath(components).getPath();
	}
	else if (isAbsolutePath())
		return separator;
	else
		return std::string(".");
}

std::string FilePath::getFileExtension (void) const
{
	std::string baseName = getBaseName();
	size_t dotPos = baseName.find_last_of('.');
	if (dotPos == std::string::npos)
		return std::string("");
	else
		return baseName.substr(dotPos+1);
}

bool FilePath::exists (void) const
{
	FilePath	normPath	= FilePath::normalize(*this);
	struct		stat		st;
	int			result		= stat(normPath.getPath(), &st);
	return result == 0;
}

FilePath::Type FilePath::getType (void) const
{
	FilePath	normPath	= FilePath::normalize(*this);
	struct		stat		st;
	int			result		= stat(normPath.getPath(), &st);

	if (result != 0)
		return TYPE_UNKNOWN;

	int type = st.st_mode & S_IFMT;
	if (type == S_IFREG)
		return TYPE_FILE;
	else if (type == S_IFDIR)
		return TYPE_DIRECTORY;
	else
		return TYPE_UNKNOWN;
}

bool FilePath::beginsWithDrive (void) const
{
	for (int ndx = 0; ndx < (int)m_path.length(); ndx++)
	{
		if (m_path[ndx] == ':' && ndx+1 < (int)m_path.length() && isSeparator(m_path[ndx+1]))
			return true; // First part is drive letter.
		if (isSeparator(m_path[ndx]))
			return false;
	}
	return false;
}

bool FilePath::isAbsolutePath (void) const
{
	return isRootPath() || isWinNetPath() || beginsWithDrive();
}

void FilePath_selfTest (void)
{
	DE_TEST_ASSERT(!FilePath(".").isAbsolutePath());
	DE_TEST_ASSERT(!FilePath("..\\foo").isAbsolutePath());
	DE_TEST_ASSERT(!FilePath("foo").isAbsolutePath());
	DE_TEST_ASSERT(FilePath("\\foo/bar").isAbsolutePath());
	DE_TEST_ASSERT(FilePath("/foo").isAbsolutePath());
	DE_TEST_ASSERT(FilePath("\\").isAbsolutePath());
	DE_TEST_ASSERT(FilePath("\\\\net\\loc").isAbsolutePath());
	DE_TEST_ASSERT(FilePath("C:\\file.txt").isAbsolutePath());
	DE_TEST_ASSERT(FilePath("c:/file.txt").isAbsolutePath());

	DE_TEST_ASSERT(string(".") == FilePath(".//.").normalize().getPath());
	DE_TEST_ASSERT(string(".") == FilePath(".").normalize().getPath());
	DE_TEST_ASSERT((string("..") + FilePath::separator + "test") == FilePath("foo/../bar/../../test").normalize().getPath());
	DE_TEST_ASSERT((FilePath::separator + "foo" + FilePath::separator + "foo.txt") == FilePath("/foo\\bar/..\\dir\\..\\foo.txt").normalize().getPath());
	DE_TEST_ASSERT((string("c:") + FilePath::separator + "foo" + FilePath::separator + "foo.txt") == FilePath("c:/foo\\bar/..\\dir\\..\\foo.txt").normalize().getPath());
	DE_TEST_ASSERT((FilePath::separator + FilePath::separator + "foo" + FilePath::separator + "foo.txt") == FilePath("\\\\foo\\bar/..\\dir\\..\\foo.txt").normalize().getPath());

	DE_TEST_ASSERT(FilePath("foo/bar"		).getBaseName()	== "bar");
	DE_TEST_ASSERT(FilePath("foo/bar/"		).getBaseName()	== "bar");
	DE_TEST_ASSERT(FilePath("foo\\bar"		).getBaseName()	== "bar");
	DE_TEST_ASSERT(FilePath("foo\\bar\\"	).getBaseName()	== "bar");
	DE_TEST_ASSERT(FilePath("foo/bar"		).getDirName()	== "foo");
	DE_TEST_ASSERT(FilePath("foo/bar/"		).getDirName()	== "foo");
	DE_TEST_ASSERT(FilePath("foo\\bar"		).getDirName()	== "foo");
	DE_TEST_ASSERT(FilePath("foo\\bar\\"	).getDirName()	== "foo");
}

static void createDirectoryImpl (const char* path)
{
#if (DE_OS == DE_OS_WIN32)
	if (!CreateDirectory(path, DE_NULL))
		throw std::runtime_error("Failed to create directory");
#elif (DE_OS == DE_OS_UNIX) || (DE_OS == DE_OS_OSX) || (DE_OS == DE_OS_IOS) || (DE_OS == DE_OS_ANDROID) || (DE_OS == DE_OS_SYMBIAN)
	if (mkdir(path, 01777) != 0)
		throw std::runtime_error("Failed to create directory");
#else
#	error Implement createDirectoryImpl() for your platform.
#endif
}

void createDirectory (const char* path)
{
	FilePath	dirPath		= FilePath::normalize(path);
	FilePath	parentPath	(dirPath.getDirName());

	if (dirPath.exists())
		throw std::runtime_error("Destination exists already");
	else if (!parentPath.exists())
		throw std::runtime_error("Parent directory doesn't exist");
	else if (parentPath.getType() != FilePath::TYPE_DIRECTORY)
		throw std::runtime_error("Parent is not directory");

	createDirectoryImpl(path);
}

void createDirectoryAndParents (const char* path)
{
	std::vector<std::string>	createPaths;
	FilePath					curPath		(path);

	if (curPath.exists())
		throw std::runtime_error("Destination exists already");

	while (!curPath.exists())
	{
		createPaths.push_back(curPath.getPath());

		std::string parent = curPath.getDirName();
		DE_CHECK_RUNTIME_ERR(parent != curPath.getPath());
		curPath = FilePath(parent);
	}

	// Create in reverse order.
	for (std::vector<std::string>::const_reverse_iterator parentIter = createPaths.rbegin(); parentIter != createPaths.rend(); parentIter++)
		createDirectory(parentIter->c_str());
}

} // de
